
#include "db/indexarray.hxx"
#include "db/indexarraymapping.hxx"
#include "util/io.hxx"
#include "util/serialize.hxx"
#include "string.h"
#include "util/fs.hxx"
#include <iostream>
#include <assert.h>
#include <errno.h>


const uint64_t MAGIC_NUM = 0x31415926abcd;

IndexArrayHeader::IndexArrayHeader(int arrayLength, int elementSize)
: m_version(0), 
  m_magic(MAGIC_NUM), 
  m_hwm(0), 
  m_lwm(0), 
  m_length(arrayLength),
  m_elemSize(elementSize) {

      m_version = 0;
      m_magic = MAGIC_NUM;
      m_hwm = 0;
      m_lwm = 0;
      m_length = arrayLength;
      m_elemSize = elementSize;

}


IndexArrayHeader::IndexArrayHeader()
: m_version(0), 
  m_magic(MAGIC_NUM), 
  m_hwm(0), 
  m_lwm(0), 
  m_length(0),
  m_elemSize(0) {

}


std::string IndexArrayHeader::serialize() {

	std::string result;
	result.append((char*)&m_version, sizeof(m_version));
	result.append((char*)&m_magic, sizeof(m_magic));
	result.append((char*)&m_hwm, sizeof(m_hwm));
	result.append((char*)&m_lwm, sizeof(m_lwm));
	result.append((char*)&m_length, sizeof(m_length));
	result.append((char*)&m_elemSize, sizeof(m_elemSize));

	return result;
}


void IndexArrayHeader::deserialize(const std::string& data) {

	Deserializer des((char*)data.c_str(), data.size());

	m_version = des.readLong();
	m_magic = des.readLong();
	m_hwm = des.readLong();
	m_lwm = des.readLong();
	m_length = des.readInt();
	m_elemSize = des.readInt();
}


std::string IndexArrayHeader::toString() {

   InfoSerializer  ser;

   ser.appendLong("version", m_version);
   ser.appendLong("magic", m_magic);
   ser.appendLong("hwm", m_hwm);
   ser.appendLong("lwm", m_lwm);
   ser.appendInt("arraylength", m_length);
   ser.appendInt("element size", m_elemSize);

   return ser.str();
}


bool IndexArrayHeader::checkHeader() {

	if(m_magic != MAGIC_NUM || m_hwm < m_lwm || m_length == 0 || m_elemSize == 0) return false;
    return true;
}


uint32_t IndexArrayFile::INDEXARRAY_HEADER_LENGTH = 1024;
uint32_t IndexArrayFile::INDEXDATA_START_OFFSET = IndexArrayFile::INDEXARRAY_HEADER_LENGTH;


IndexArrayFile::IndexArrayFile(const std::string& partitionPath, int arrayLength, int elementSize)
: m_raf(NULL), 
  m_header(arrayLength, elementSize), 
  m_iaBuf(NULL)	{

	m_indexArrayFile = partitionPath + "/index.db";
}

IndexArrayFile::IndexArrayFile(const std::string& indexFilePath) 
: m_raf(NULL), 
  m_iaBuf(NULL) {

    m_indexArrayFile = indexFilePath;
}


IndexArrayFile::~IndexArrayFile() {

	if(m_raf) { delete m_raf; m_raf = NULL;}
    if(m_iaBuf) {delete[] m_iaBuf; m_iaBuf = NULL; }
}


DbStatus IndexArrayFile::init() {

    DbStatus ret;

    uint32_t arrayLength = m_header.getArrayLength();
    uint32_t elementSize = m_header.getElementSize();

	bool newfile = false;
	if(!FsUtility::fileExists(m_indexArrayFile)) {
	
		newfile = true;
	}
	
	//m_raf = new PosixRandomAccessFile(m_indexArrayFile);
    if(arrayLength > 0 && elementSize > 0) {

        int fileSize = IndexArrayFile::INDEXARRAY_HEADER_LENGTH + arrayLength * elementSize;
        m_raf = new PosixMmapRandomAccessFile(m_indexArrayFile, fileSize);
        m_raf->init(); if(!ret.isok()) return ret;
    }
    else {

        int fileSize = IndexArrayFile::INDEXARRAY_HEADER_LENGTH;
        m_raf = new PosixMmapRandomAccessFile(m_indexArrayFile, fileSize);
        m_raf->init(); if(!ret.isok()) return ret;
    }
	
	if(newfile) {
    
        m_iaBuf = new uint64_t[arrayLength];
        memset(m_iaBuf, 0, sizeof(uint64_t)*arrayLength);
		ret = saveHeader(); 
        if(!ret.isok()) return ret;
        return saveData();
	}
	else {
	
		ret = loadHeader(); 
        if(ret.getCode() == DbStatus::DB_PARTIALREAD || ret.getCode() ==
                DbStatus::DB_CORRUPTION) {
        
            ret = saveHeader();
        }
        if(!ret.isok()) return ret;

        //check length and element size are valid
        if( (arrayLength || elementSize) && (arrayLength != m_header.getArrayLength() || elementSize !=
                m_header.getElementSize()) ) {

            return DbStatus("IndexArrayFile::init", EINVAL);
        }

        //if raf was initilaized just with header size, we need re-initialize with file size
        if(arrayLength == 0 || elementSize == 0) {
            
            m_raf->close();
            delete m_raf;
            int fileSize = IndexArrayFile::INDEXARRAY_HEADER_LENGTH + m_header.getArrayLength() * m_header.getElementSize();
            m_raf = new PosixMmapRandomAccessFile(m_indexArrayFile, fileSize);
            m_raf->init(); if(!ret.isok()) return ret; 
        }

        m_iaBuf = new uint64_t[m_header.getArrayLength()];
        ret = loadData();

        if(ret.getCode() == DbStatus::DB_PARTIALREAD) {
            
            m_iaBuf = new uint64_t[arrayLength];
            memset(m_iaBuf, 0, sizeof(uint64_t)*arrayLength);
        }
	}

	return ret;
}

DbStatus IndexArrayFile::loadHeader() {

	std::string header;
	DbStatus ret = m_raf->read(0, INDEXARRAY_HEADER_LENGTH, header);
	if(!ret.isok()) return ret;

	m_header.deserialize(header);

	bool isvalid = m_header.checkHeader();
	if(!isvalid) return DbStatus("IndexArrayFile::loadHeader", DbStatus::DB_CORRUPTION);

	return DbStatus("IndexArrayFile::loadHeader", 0);
}

DbStatus IndexArrayFile::saveHeader() {

	std::string header = m_header.serialize();
	DbStatus ret = m_raf->write(0,  header); 
	if(!ret.isok()) return ret;

	ret = m_raf->flush();
	return ret;
}

DbStatus IndexArrayFile::loadData() {

	uint32_t offset = INDEXDATA_START_OFFSET;
	
	for(int i = 0; i < m_header.getArrayLength(); ++i, offset +=
            m_header.getElementSize()) {
	
		std::string result;
		DbStatus ret = m_raf->read(offset, m_header.getElementSize(), result);
		if(!ret.isok()) return ret;
		
		if(result.size() != m_header.getElementSize()) return DbStatus("IndexArrayFile::loadData", DbStatus::DB_CORRUPTION);
		memcpy((void*)&m_iaBuf[i], (void*)result.c_str(), sizeof(uint64_t));
	}

	return DbStatus("IndexArrayFile::loadData", 0);
}

DbStatus IndexArrayFile::saveData() {

	uint32_t offset = INDEXDATA_START_OFFSET;
    return m_raf->write(offset, (char*)m_iaBuf, m_header.getArrayLength() * m_header.getElementSize());
/*
	for(int i = 0; i < m_header.getArrayLength(); ++i, offset += m_header.getElementSize())
	{
		DbStatus ret = m_raf->write(offset, (char*)&m_iaBuf[i], m_header.getElementSize());
		if(!ret.isok()) return ret;
		
    }
    return m_raf->flush();	
*/
}

DbStatus IndexArrayFile::writeLong(uint32_t index, uint64_t value) {

    DbStatus ret;

	uint32_t offset = INDEXDATA_START_OFFSET + index * m_header.getElementSize();
	ret = m_raf->write(offset, (char*)&value, m_header.getElementSize());
	if(!ret.isok()) return ret;

    return ret;
}

DbStatus IndexArrayFile::flush() {

	DbStatus ret =  m_raf->flush(); if(!ret.isok()) return ret;
    return saveHeader();
}

uint64_t IndexArrayFile::get(uint32_t index) {

    return  m_iaBuf[index];
}

void IndexArrayFile::set(uint32_t index, uint64_t value, uint64_t scn) {

    m_iaBuf[index] = value;
    m_header.setWaterMarks(m_header.getLwmScn(), scn);
}

void IndexArrayFile::set(uint32_t index, uint64_t value) {

    m_iaBuf[index] = value;
}


DbStatus IndexArrayFile::applyEntries(IndexArrayMapping& entries) {
    DbStatus ret;

    for(int i = 0; i < m_header.getArrayLength(); i += entries.getSectionSize() ) {
      
        if(!entries.test(i)) continue;

        uint32_t offset = INDEXDATA_START_OFFSET + i * sizeof(uint64_t);
        uint32_t length = entries.getSectionSize();
        if(i + length >= m_header.getArrayLength()) 
            length = m_header.getArrayLength() - i;

        ret = m_raf->write(offset, (char*)&m_iaBuf[i], length * sizeof(uint64_t) );
        if(!ret.isok())  return ret;
    }       

    return ret;
}


IndexArray::IndexArray(const std::string& partitionPath, uint32_t indexArrayCapacity)
: m_capacity(indexArrayCapacity), 
  m_partitionPath(partitionPath), 
  m_indexArrayFile(NULL),
  m_entries(NULL)  {

      pthread_mutex_init(&m_mutex, NULL);
}

IndexArray::~IndexArray() {

    pthread_mutex_destroy(&m_mutex);
    
    if(m_entries) { delete m_entries; m_entries = NULL; }
}

